﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace helicopterFight
{
    public class Terrain : GameObject
    {
        private Vector3[] respawns = {
                                        new Vector3(3700, 700, -3700),
                                        new Vector3(3050,450,-2500),
                                        new Vector3(620,860,-2970),
                                        new Vector3(2120,490,-1950),
                                        new Vector3(3900,750,-4700),
                                        new Vector3(550,704,-5000),
                                        new Vector3(6500,650,-5700)
                                     };

        public Vector3[] Respawns
        {
            get { return respawns; }
            set { respawns = value; }
        }
        private String name;
        private String difficulty;
        private String missionTime;
        private Texture2D minitexture;
        private TestState state;
        //TERRAIN DATA
        int terrainWidth;   
        public int TerrainWidth
        { 
            get { return terrainWidth; } 
            //set; 
        }

        int terrainLength;
        public int TerrainLength
        {
            get { return terrainLength; }
            //set;
        }

        private float[,] heightData;

        public float[,] HeightData
        {
            get { return heightData; }
            set { heightData = value; }
        }
        float[,] heightData2;
        private BoundingSphere[,] terrainSpheres;

        
        VertexBuffer terrainVertexBuffer;
        IndexBuffer terrainIndexBuffer;
        VertexDeclaration terrainVertexDeclaration;

        Effect effect;
        Texture2D grassTexture;
        Texture2D sandTexture;
        Texture2D rockTexture;
        Texture2D snowTexture;
        Texture2D heightMap;
        public GraphicsDevice device;
        //END TERRAIN DATA

        public struct VertexMultitextured
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector4 TextureCoordinate;
            public Vector4 TexWeights;

            public static int SizeInBytes = (3 + 3 + 4 + 4) * sizeof(float);
            public static VertexElement[] VertexElements = new VertexElement[]
         {
             new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
             new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
             new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
             new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
         };
        }

        public Terrain(Texture2D tex, String name, String difficulty, String missionTime) {
            this.minitexture = tex;
            this.name = name;
            this.difficulty = difficulty;
            this.missionTime = missionTime;
        }

        public BoundingSphere[,] TerrainSpheres
        {
            get { return terrainSpheres; }
            set { terrainSpheres = value; }
        }

        public void init(Game1 game, TestState state, int level)
        {
            this.state = state;
            effect =game.Content.Load<Effect>("models/Series4Effects");
            switch (level)
            {
                case 0: heightMap = game.Content.Load<Texture2D>("models/terrain/heightmap");
                        Vector3[] resp = {
                                        new Vector3(3700, 700, -3700),
                                        new Vector3(3050,450,-2500),
                                        new Vector3(620,860,-2970),
                                        new Vector3(2120,490,-1950),
                                        new Vector3(3900,750,-4700),
                                        new Vector3(550,704,-5000),
                                        new Vector3(6500,650,-5700)
                                     };
                        respawns = resp;
                        sandTexture = game.Content.Load<Texture2D>("models/terrain/sandx");
                        grassTexture = game.Content.Load<Texture2D>("models/terrain/grass");
                        snowTexture = game.Content.Load<Texture2D>("models/terrain/snowx");
                        rockTexture = game.Content.Load<Texture2D>("models/terrain/rockx");
                        break;
                case 1: heightMap = game.Content.Load<Texture2D>("models/terrain/heightmap2");
                        Vector3[] resp2 = {
                                        new Vector3(3700, 900, -3700),
                                        new Vector3(3050,900,-2500),
                                        new Vector3(620,900,-2970),
                                        new Vector3(2120,900,-1950),
                                        new Vector3(3900,900,-4700),
                                        new Vector3(550,900,-5000),
                                        new Vector3(6500,900,-5700)
                                     };
                        respawns = resp2;
                        sandTexture = game.Content.Load<Texture2D>("models/terrain/grass");
                        grassTexture = game.Content.Load<Texture2D>("models/terrain/sandx2");
                        snowTexture = game.Content.Load<Texture2D>("models/terrain/snowx");
                        rockTexture = game.Content.Load<Texture2D>("models/terrain/rockx");
                        break;
            }
            
            device = game.GraphicsDevice;

            LoadHeightData(heightMap);
            LoadVertices();

            terrainSpheres = new BoundingSphere[heightData.GetUpperBound(0), heightData.GetUpperBound(1)];
            for ( int x = 0; x < heightData.GetUpperBound(0); x++ )
                for (int y = 0; y < heightData.GetUpperBound(1); y++)
                {
                    terrainSpheres[x, y] = new BoundingSphere(new Vector3(x*30, y*30, heightData[x,y]*30), 8);
                }

        }

        public String getName()
        {
            return this.name;
        }

        public String getDifficulty() {
            return this.difficulty;
        }

        public String getMissionTime() {
            return this.missionTime;
        }

        public Texture2D getMiniTex() {
            return this.minitexture;
        }

        public override void draw()
        {
            //device.RenderState.CullMode = CullMode.None;
            //device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

            effect.CurrentTechnique = effect.Techniques["MultiTextured"];
            effect.Parameters["xTexture0"].SetValue(sandTexture);
            effect.Parameters["xTexture1"].SetValue(grassTexture);
            effect.Parameters["xTexture2"].SetValue(rockTexture);
            effect.Parameters["xTexture3"].SetValue(snowTexture);

            Matrix worldMatrix = Matrix.Identity;
            effect.Parameters["xWorld"].SetValue(worldMatrix);
            effect.Parameters["xView"].SetValue(cam.CameraViewMatrix);
            effect.Parameters["xProjection"].SetValue(cam.CameraProjectionMatrix);

            effect.Parameters["xEnableLighting"].SetValue(true);
            effect.Parameters["xAmbient"].SetValue(0.4f);
            effect.Parameters["xLightDirection"].SetValue(new Vector3(-0.5f, -1, -0.5f));

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                device.Vertices[0].SetSource(terrainVertexBuffer, 0, VertexMultitextured.SizeInBytes);
                device.Indices = terrainIndexBuffer;
                device.VertexDeclaration = terrainVertexDeclaration;

                int noVertices = terrainVertexBuffer.SizeInBytes / VertexMultitextured.SizeInBytes;
                int noTriangles = terrainIndexBuffer.SizeInBytes / sizeof(int) / 3;
                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, noVertices, 0, noTriangles);

                pass.End();
            }
            effect.End();
        }

        private void CopyToTerrainBuffers(VertexMultitextured[] vertices, int[] indices)
        {
            terrainVertexBuffer = new VertexBuffer(device, vertices.Length * VertexMultitextured.SizeInBytes, BufferUsage.WriteOnly);
            terrainVertexBuffer.SetData(vertices);

            terrainIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly);
            terrainIndexBuffer.SetData(indices);
        }

        private VertexMultitextured[] CalculateNormals(VertexMultitextured[] vertices, int[] indices)
        {
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();

            return vertices;
        }

        private int[] SetUpTerrainIndices()
        {
            int[] indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6];
            int counter = 0;
            for (int y = 0; y < terrainLength - 1; y++)
            {
                for (int x = 0; x < terrainWidth - 1; x++)
                {
                    int lowerLeft = x + y * terrainWidth;
                    int lowerRight = (x + 1) + y * terrainWidth;
                    int topLeft = x + (y + 1) * terrainWidth;
                    int topRight = (x + 1) + (y + 1) * terrainWidth;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }

            return indices;
        }

        private VertexMultitextured[] SetUpTerrainVertices()
        {
            VertexMultitextured[] terrainVertices = new VertexMultitextured[terrainWidth * terrainLength];

            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainLength; y++)
                {
                    heightData2[x, y] = 30*heightData[x, y];
                    terrainVertices[x + y * terrainWidth].Position = new Vector3(x*30, heightData[x, y]*30, -y*30);
                    terrainVertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / 30.0f;
                    terrainVertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / 30.0f;

                    terrainVertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 0) / 8.0f, 0, 1);
                    terrainVertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 12) / 6.0f, 0, 1);
                    terrainVertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 20) / 6.0f, 0, 1);
                    terrainVertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 30) / 6.0f, 0, 1);

                    float total = terrainVertices[x + y * terrainWidth].TexWeights.X;
                    total += terrainVertices[x + y * terrainWidth].TexWeights.Y;
                    total += terrainVertices[x + y * terrainWidth].TexWeights.Z;
                    total += terrainVertices[x + y * terrainWidth].TexWeights.W;

                    terrainVertices[x + y * terrainWidth].TexWeights.X /= total;
                    terrainVertices[x + y * terrainWidth].TexWeights.Y /= total;
                    terrainVertices[x + y * terrainWidth].TexWeights.Z /= total;
                    terrainVertices[x + y * terrainWidth].TexWeights.W /= total;
                }
            }

            return terrainVertices;
        }

        private void LoadHeightData(Texture2D heightMap)
        {
            float minimumHeight = float.MaxValue;
            float maximumHeight = float.MinValue;

            terrainWidth = heightMap.Width;
            terrainLength = heightMap.Height;

            Color[] heightMapColors = new Color[terrainWidth * terrainLength];
            heightMap.GetData(heightMapColors);

            heightData = new float[terrainWidth, terrainLength];
            heightData2 = new float[terrainWidth, terrainLength];
            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainLength; y++)
                {
                    heightData[x, y] = heightMapColors[x + y * terrainWidth].R;
                    if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y];
                    if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y];
                }

            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainLength; y++)
                    heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * 30.0f;
        }

        private void LoadVertices()
        {
            VertexMultitextured[] terrainVertices = SetUpTerrainVertices();
            int[] terrainIndices = SetUpTerrainIndices();
            terrainVertices = CalculateNormals(terrainVertices, terrainIndices);
            CopyToTerrainBuffers(terrainVertices, terrainIndices);
            terrainVertexDeclaration = new VertexDeclaration(device, VertexMultitextured.VertexElements);
        }
    }
}
